﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Sockets;
using System.Threading;
using MyUtils;

namespace FitAutomation.FitProcess
{
	/// <summary>
	/// Proxy for accessing objects in another process
	/// </summary>
	public class FitProcessAccessor : HideObjectMembers, IDisposable //, IFitProcessHost
	{
		readonly int _port;
		readonly Process _pi;
		readonly TcpClient _client;

		internal FitProcessAccessor(int port, Process pi)
		{
			_port = port;
			_pi = pi;
			_client = new TcpClient
			{
				NoDelay = true,
				ExclusiveAddressUse = true,
				// ReceiveBufferSize = 1024,
				// SendBufferSize = 1024,
				// ReceiveTimeout = 2000,
				// SendTimeout = 2000,
			};
		}

		internal void Connect()
		{
			_client.Connect("localhost", _port);
		}

		internal Message Send(Message message)
		{
			if (_client.Connected)
			{
				_client.Send(message);
				return _client.Receive();
			}
			return null;
		}

		EventWaitHandle _eventStopping;

		void ProcessExited(object sender, EventArgs eventArgs)
		{
			_eventStopping.Set();
		}

		/// <summary>
		/// Close process
		/// </summary>
		public void Close()
		{
			using (_eventStopping = new EventWaitHandle(false, EventResetMode.ManualReset))
			{
				_pi.EnableRaisingEvents = true;
				_pi.Exited += ProcessExited;

				try
				{
					Send(MessageCode.CloseProcess);
				}
				catch
				{
				}
				try
				{
					_client.Close();
				}
				catch
				{
				}

				_eventStopping.WaitOne();

				_pi.Exited -= ProcessExited;

				Debug.Assert(_pi.HasExited);
				// Utils.WaitFor(() => _pi.HasExited);
			}
			_eventStopping = null;
		}

		void IDisposable.Dispose()
		{
			Close();
		}

		/// <summary>
		/// Create new object inside another process
		/// </summary>
		public ObjectAccessor NewObject<T>()
		{
			var name = Guid.NewGuid().ToString();
			NewObject<T>(name);
			return this[name];
		}

		/// <summary>
		/// Create new object inside another process
		/// </summary>
		public void NewObject<T>(string objName)
		{
			NewObject(objName, typeof(T));
		}

		/// <summary>
		/// Create new object inside another process
		/// </summary>
		public void NewObject(string objName, Type type)
		{
			Send("new;" + objName + ";" + type.AssemblyQualifiedName);
		}

		/// <summary>
		/// Get object accessor for object in another process
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public ObjectAccessor this[string name]
		{
			get { return new ObjectAccessor(this, name); }
		}

	}
}